<template>
  <div>
    <h1>Ollama API 流式返回 POST 示例</h1>
    <button @click="fetchStream">发送 POST 请求</button>
    <div class="chunkBox" v-html="response"></div>
  </div>
</template>

<script>
export default {
  data () {
    return {
      response: '', // 用于存储和显示流式返回的数据,
      ceshi: '123/n123'
    }
  },
  methods: {
    /**
     * 请求每次返回一个或多个对象，有时也会返回半个对象，所以不要直接使用json解析，用一个字符串存一下，并且让返回的每一个完整对象用 \n 分隔一下，然后存一个全局变量去拼接接收所有对象，
     * 然后截取到最后一个 \n 去渲染。这样就不会有json解析异常的问题，详情见 addChat2
     *
     */
    async fetchStream () {
      try {
        // 构建 POST 请求
        const response = await fetch(
          'http://39.170.92.214:11434/api/generate',
          {
            method: 'POST', // POST 请求
            headers: {
              'Content-Type': 'application/json' // 根据需要设置头部
            },
            body: JSON.stringify({
              model: 'qwen2.5:14b-instruct',
              stream: true,
              prompt: '介绍一下自己' // 示例提示信息，根据 API 要求构建请求体
            })
          }
        )

        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`)
        }

        // 获取流并解码
        const reader = response.body.getReader()
        console.log('reader', reader)
        const decoder = new TextDecoder()
        this.response = '' // 清空之前的响应
        let resStr = ''

        // 循环读取流数据
        while (true) {
          const { done, value } = await reader.read()
          if (done) break // 流结束

          // 将流数据解码为字符串并添加到响应中
          let chunk = decoder.decode(value, { stream: true })
          resStr += chunk
          console.log(resStr)
          // this.response += chunk // 逐步更新数据

          // 如果需要，可以在这里进行其他操作，比如部分更新 UI
          // console.log('Received chunk:', chunk)
          try {
            const jsonData = JSON.parse(chunk)
            // 这里 jsonData 就是解析后的JSON对象，可以根据实际情况使用
            // console.log('Parsed JSON data:', jsonData)
            this.response += jsonData.response
          } catch (parseError) {
            // 如果解析失败，说明这个chunk可能不是完整的JSON，或者不是JSON格式
            // console.log('Could not parse chunk as JSON:', chunk)
            while (chunk.indexOf('response') > -1) {
              const startIndex =
                chunk.indexOf('"response":"') + '"response":"'.length
              const endIndex = chunk.indexOf('"', startIndex)
              const responseValue = chunk.slice(startIndex, endIndex)
              this.response += responseValue
              chunk = chunk.slice(endIndex)
            }
          }
        }

        console.log('Stream ended') // 流结束后的处理
      } catch (error) {
        if (error.name !== 'AbortError') {
          // 如果不是手动取消
          console.error('Fetch error:', error)
        }
      }
    },
    async addChat2 (message) {
      this.showLoading = true
      const response = await fetch(
        // `${projectConfig.baseUrl}admin/eqm/ai/chat/stream`,
        {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            Authorization: getToken()
          },
          body: JSON.stringify({
            slug: this.slug,
            message: message,
            type: this.messageType
          })
        }
      )

      // 获取流并解码
      const reader = response.body.getReader()
      const decoder = new TextDecoder()
      let buffer = ''
      let index = 0
      this.messages.push({
        role: 'assistant',
        content: this.responseStr,
        sources: [],
        sourcesShow: false
      })

      // 循环读取流数据
      while (true) {
        const { value } = await reader.read()
        if (value == undefined) {
          break
        }
        const chunk = decoder.decode(value, { stream: true })
        buffer += chunk

        try {
          const eolIndex = buffer.lastIndexOf('\n')
          if (eolIndex == -1) {
            continue
          }
          let objects = buffer.slice(0, eolIndex).split('\n')
          objects = objects.map((item) => JSON.parse(item))

          for (let i = index; i < objects.length; i++) {
            this.responseStr += objects[i].response
            this.$set(
              this.messages[this.messages.length - 1],
              'content',
              // this.formatMessage(this.responseStr)
              this.responseStr
            )
            this.$nextTick((_) => {
              this.showLoading = false
              const chatContainer = document.getElementById('chatContainer') // 替换为实际的元素 ID
              chatContainer.scrollTop = chatContainer.scrollHeight
            })
            await new Promise((resolve) => setTimeout(resolve, 50))

            if (objects[i].done) {
              this.$set(
                this.messages[this.messages.length - 1],
                'sources',
                // this.formatMessage(objects[i].sourceDocuments)
                objects[i].sources
              )
              console.log('this.messages信息：')
              console.log(this.messages)
              this.$nextTick((_) => {
                this.showLoading = false
                const chatContainer = document.getElementById('chatContainer') // 替换为实际的元素 ID
                chatContainer.scrollTop = chatContainer.scrollHeight
              })
              break // 流结束
            }
          }
          index = objects.length
        } catch (parseError) {
          // 如果解析失败，说明这个chunk可能不是完整的JSON，或者不是JSON格式
          console.log('Could not parse chunk as JSON:')
          console.log(chunk)
          this.showLoading = false
        }
      }
    }
  }
}
</script>

<style scoped>
.chunkBox {
  height: 50vh;
  width: 90%;
  border: 1px solid green;
}
</style>
